文件上传下载,这样做最简单~
大家好,我是程序员鱼皮,前段时间带大家做的 代码生成平台项目 中,需要用到文件上传下载功能。这是我们在开发项目中常用的 通用能力,所以我单独从项目教程中抽了一节小教程,给大家分享一种文件上传下载的实现方式,非常简单!
照顾移动端同学的阅读感受,以下为部分教程,完整教程和代码可以在我的 编程宝典网站(codefather.cn)或 编程导航 中查看。
基本思路
首先我们要思考:将文件上传到哪里?从哪里下载?
最简单的方式就是上传到后端项目所在的服务器,直接使用 Java 自带的文件读写 API 就能实现。但是,这种方式存在不少缺点,比如:
不利于扩展:单个服务器的存储是有限的,如果存满了,只能再新增存储空间或者清理文件。 不利于迁移:如果后端项目要更换服务器部署,之前所有的文件都要迁移到新服务器,非常麻烦。 不够安全:如果忘记控制权限,用户很有可能通过恶意代码访问服务器上的文件,而且想控制权限也比较麻烦,需要自己实现。 不利于管理:只能通过一些文件管理器进行简单的管理操作,但是缺乏数据处理、流量控制等多种高级能力。
因此,除了存储一些需要清理的临时文件之外,我们通常不会将用户上传并保存的文件(比如用户头像)直接上传到服务器,而是更推荐大家使用专业的第三方存储服务,专业的工具做专业的事。其中,最常用的便是 对象存储 。
什么是对象存储?
对象存储是一种存储 海量文件 的 分布式 存储服务,具有高扩展性、低成本、可靠安全等优点。
比如开源的对象存储服务 MinIO,还有商业版的云服务,像亚马逊 S3(Amazon S3)、阿里云对象存储(OSS)、腾讯云对象存储(COS)等等。
我个人更推荐大家使用第三方云服务,不要自己再去搭建 MinIO 之类的,咱学习主打一个快速!
本教程中,将用腾讯云的 COS 带大家实现文件的上传和下载。
创建并使用
首先进入对象存储控制台,创建存储桶。
可以把存储桶理解为一个存储空间,和文件系统类似,都是根据路径找到文件或目录。可以多个项目共用一个存储桶,也可以每个项目一个。
点击创建存储桶,注意地域选择国内(离用户较近的位置)。此处访问权限先选择“公有读私有写”,因为我们的存储桶要存储允许用户公开访问的代码生成器图片。而如果整个存储桶要存储的文件都不允许用户访问,建议选择私有读写,更安全。
默认告警一定要勾选! 因为对象存储服务的存储和访问流量都是计费的,超限后我们要第一时间得到通知并进行相应的处理。
不过也不用太担心,自己做项目的话一般是没人攻击你的,而且对象存储很便宜,正常情况下消耗的费用寥寥无几。
然后一直点击“下一步”即可。
开通成功后,我们可以试着使用 web 控制台上传和浏览文件。当然,一般情况下我们会使用程序来操作存储桶,下面就来实现。
后端操作对象存储
如何在 Java 程序中使用对象存储呢?
其实非常简单,一般情况下,第三方服务都会提供比较贴心的文档教程,比如这里我们参考官方的快速入门或 Java SDK 文档,就能快速入门基本操作(增删改查都有)。
文档地址:https://cloud.tencent.com/document/product/436/10199
1、初始化客户端
参考官方文档,我们要先初始化一个 COS 客户端对象,和对象存储服务进行交互。
对于规模不大的项目,只需要复用一个 COS 客户端对象即可,所以我们可以通过编写配置类初始化客户端对象。
1)打开 Spring Boot 项目,在 config 目录下新建 CosClientConfig
类。负责读取配置文件,并创建一个 COS 客户端的 Bean。
示例代码如下:
@Configuration
@ConfigurationProperties(prefix = "cos.client")
@Data
public class CosClientConfig {
/**
* accessKey
*/
private String accessKey;
/**
* secretKey
*/
private String secretKey;
/**
* 区域
*/
private String region;
/**
* 桶名
*/
private String bucket;
@Bean
public COSClient cosClient() {
// 初始化用户身份信息(secretId, secretKey)
COSCredentials cred = new BasicCOSCredentials(accessKey, secretKey);
// 设置bucket的区域
ClientConfig clientConfig = new ClientConfig(new Region(region));
// 生成cos客户端
return new COSClient(cred, clientConfig);
}
}
2)填写配置文件。
一定要注意防止密码泄露! 所以我们新建 application-local.yml
文件,并且在 .gitignore
中忽略该文件的提交,这样就不会将代码等敏感配置提交到代码仓库了。
配置代码如下:
# 本地配置文件
# 对象存储
cos:
client:
accessKey: xxx
secretKey: xxx
region: xxx
bucket: xxx
可以参考官方文档分别获取需要的配置。
2、通用能力类
新建 CosManager
类,提供通用的对象存储操作,比如文件上传、文件下载等,供其他代码(比如 Service)调用。
该类需要引入对象存储配置和 COS 客户端,用于和 COS 进行交互。参考代码如下:
@Component
public class CosManager {
@Resource
private CosClientConfig cosClientConfig;
@Resource
private COSClient cosClient;
... 一些操作 COS 的方法
}
3、文件上传
参考官方文档的“上传对象”部分,可以编写出文件上传的代码。
1)CosManager
新增上传对象的方法,代码如下:
/**
* 上传对象
*
* @param key 唯一键
* @param file 文件
* @return
*/
public PutObjectResult putObject(String key, File file) {
PutObjectRequest putObjectRequest = new PutObjectRequest(
cosClientConfig.getBucket(), key, file);
return cosClient.putObject(putObjectRequest);
}
2)用一个常量类 FileConstant
记录 COS 访问域名,便于接下来测试访问已上传的文件。
示例代码如下:
public interface FileConstant {
/**
* COS 访问地址
*/
String COS_HOST = "自己的 COS 访问地址,云服务控制台可查";
}
3)为了方便测试,在 FileController
中编写测试文件上传接口。
核心流程是先接受用户上传的文件,指定上传的路径,然后调用 cosManager.putObject
方法上传文件到 COS 对象存储;上传成功后,会返回一个文件的 key(其实就是文件路径),便于我们访问和下载文件。
测试文件上传接口代码如下:
@PostMapping("/test/upload")
public BaseResponse<String> testUploadFile(@RequestPart("file") MultipartFile multipartFile) {
// 文件目录
String filename = multipartFile.getOriginalFilename();
String filepath = String.format("/test/%s", filename);
File file = null;
try {
// 上传文件
file = File.createTempFile(filepath, null);
multipartFile.transferTo(file);
cosManager.putObject(filepath, file);
// 返回可访问地址
return ResultUtils.success(filepath);
} catch (Exception e) {
log.error("file upload error, filepath = " + filepath, e);
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "上传失败");
} finally {
if (file != null) {
// 删除临时文件
boolean delete = file.delete();
if (!delete) {
log.error("file delete error, filepath = {}", filepath);
}
}
}
}
需要注意,如果是线上项目,测试接口一定要加上管理员权限!防止任何用户随意上传文件。
4)测试接口
使用 local 配置启动项目:
打开 Swagger 接口文档,测试文件上传:
4、文件下载
官方文档介绍了 2 种文件下载方式。一种是直接下载 COS 的文件到后端服务器(适合服务器端处理文件),另一种是获取到文件下载输入流(适合返回给前端用户)。
其实还有第三种“下载方式”,直接通过路径链接访问,适用于单一的、可以被用户公开访问的资源,比如用户头像、本项目中的代码生成器图片。
1)首先在 CosManager
中新增对象下载方法,根据对象的 key 获取存储信息:
public COSObject getObject(String key) {
GetObjectRequest getObjectRequest = new GetObjectRequest(cosClientConfig.getBucket(), key);
return cosClient.getObject(getObjectRequest);
}
2)为了方便测试,在 FileController
中编写测试文件下载接口。
核心流程是根据路径获取到 COS 文件对象,然后将文件对象转换为文件流,并写入到 Servlet 的 Response 对象中。注意要设置文件下载专属的响应头。
测试文件下载接口代码如下:
@GetMapping("/test/download/")
public void testDownloadFile(String filepath, HttpServletResponse response) throws IOException {
COSObjectInputStream cosObjectInput = null;
try {
COSObject cosObject = cosManager.getObject(filepath);
cosObjectInput = cosObject.getObjectContent();
// 处理下载到的流
byte[] bytes = IOUtils.toByteArray(cosObjectInput);
// 设置响应头
response.setContentType("application/octet-stream;charset=UTF-8");
response.setHeader("Content-Disposition", "attachment; filename=" + filepath);
// 写入响应
response.getOutputStream().write(bytes);
response.getOutputStream().flush();
} catch (Exception e) {
log.error("file download error, filepath = " + filepath, e);
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "下载失败");
} finally {
if (cosObjectInput != null) {
cosObjectInput.close();
}
}
}
3)启动项目,打开 Swagger 接口文档,测试文件下载:
至此,后端操作对象存储的代码已编写完成,下面写一个前端页面来测试文件的上传和下载。
前端文件上传 / 下载
1)首先使用 openAPI 工具生成接口(或者自己编写类似的代码)。
不知道怎么生成前端代码的同学,可以去补下 鱼皮的项目教程 。
可以看到工具为我们生成了文件上传请求函数,等会儿直接用就行,爽歪歪~
2)新建文件上传下载测试页面,并添加路由。
3)和后端一样,新增对象存储相关常量。
/**
* COS 访问地址
*/
export const COS_HOST = "https://yuzi-1256524210.cos.ap-shanghai.myqcloud.com";
4)开发页面。
对于文件上传,直接使用 Ant Design 的 Upload 拖拽文件上传组件。
对于文件下载,使用 img
标签直接拼接图片地址并展示、再写一个按钮来触发文件下载。
页面效果如图:
文件上传参考代码如下,我们在 customRequest
字段中自定义了上传文件的请求逻辑:
const props: UploadProps = {
name: 'file',
multiple: false,
maxCount: 1,
customRequest: async (fileObj: any) => {
try {
const res = await testUploadFileUsingPost({}, fileObj.file);
fileObj.onSuccess(res.data);
setValue(res.data);
} catch (e: any) {
message.error('上传失败,' + e.message);
fileObj.onError(e);
}
},
onRemove() {
setValue(undefined);
},
};
<Card title="文件上传">
<Dragger {...props}>
<p className="ant-upload-drag-icon">
<InboxOutlined />
</p>
<p className="ant-upload-text">Click or drag file to this area to upload</p>
<p className="ant-upload-hint">
Support for a single or bulk upload. Strictly prohibited from uploading company data or
other banned files.
</p>
</Dragger>
</Card>
对于文件下载,可以使用 file-saver
库,将后端返回的 blob 内容转化为文件。
先安装 file-saver
库:
npm install file-saver
npm i --save-dev @types/file-saver
下载文件代码如下:
import { saveAs } from 'file-saver';
<Button
onClick={async () => {
const blob = await testDownloadFileUsingGet({
filepath: value,
}, {
responseType: "blob",
});
// 使用 file-saver 来保存文件
const fullPath = COS_HOST + value;
saveAs(blob, fullPath.substring(fullPath.lastIndexOf("/") + 1));
}}
>
点击下载文件
</Button>
至此,通用的文件上传和下载功能已开发完成。核心代码都给了,大家感兴趣自己实践下。
实践
鱼皮原创的定制化代码生成项目中,演示了如何使用对象存储实现代码生成器文件的上传和下载。
感兴趣的同学欢迎加入 编程导航 ,跟着鱼皮一起学编程、做项目~
👇🏻 点击下方阅读原文,获取鱼皮往期编程干货。
往期推荐